// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

// ************************************************************************* //
//                            avtSAMIFileFormat.C                           //
// ************************************************************************* //
#include <silo.h>

#include <avtSAMIFileFormat.h>

#include <string>
#include <vector>

#include <vtkCellTypes.h>
#include <vtkFloatArray.h>
#include <vtkIntArray.h>
#include <vtkPoints.h>
#include <vtkRectilinearGrid.h>
#include <vtkType.h>
#include <vtkUnstructuredGrid.h>

#include <avtDatabaseMetaData.h>
#include <avtMaterial.h>

#include <DebugStream.h>
#include <InvalidFilesException.h>
#include <InvalidVariableException.h>

using     std::string;
using     std::vector;


static int
SizeOfType(int vtkType)
{
    switch (vtkType)
    {
        case VTK_SHORT: return sizeof(short); 
        case VTK_INT: return sizeof(int);
        case VTK_LONG: return sizeof(long);
        case VTK_FLOAT: return sizeof(float);
        case VTK_DOUBLE: return sizeof(double);
        case VTK_CHAR: return sizeof(char);
    } 
    return 0;
}


static int
SiloTypeToVTKType(int siloType)
{
    switch (siloType)
    {
        case DB_SHORT: return VTK_SHORT;
        case DB_INT: return VTK_INT;
        case DB_LONG: return VTK_LONG;
        case DB_FLOAT: return VTK_FLOAT;
        case DB_DOUBLE: return VTK_DOUBLE;
        case DB_CHAR: return VTK_CHAR;
    } 
    return -1;
}

// ****************************************************************************
//  Function: ExceptionGenerator
//
//  Purpose: Generates a Silo exception so that Silo's error handling can hook into
//  VisIt's error handling library.
//
//  Programmer: Mark C. Miller stolen from Hank Childs in Silo plugin
//  Creation:   Mon Nov 28 16:41:58 PST 2011
//
// ****************************************************************************
static void
ExceptionGenerator(char *msg)
{
    if (msg)
    {
        debug1 << "The following Silo error occurred: " << msg << endl;
    }
    else
    {
        debug1 << "A Silo error occurred, but the Silo library did not "
               << "generate an error message." << endl;
    }
}

// ****************************************************************************
//  Method: avtSAMI constructor
//
//  Programmer: miller -- generated by xml2avt
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
//  Modifications:
//    Mark C. Miller, Mon Oct 16 13:20:06 PDT 2006
//    Made it more tolerant of Silo read errors and more descriptive error
//    messages.
//
//    Mark C. Miller, Tue Nov 21 10:39:12 PST 2006
//    Fixed error reading from 'meshData' after freeing it
//
//    Mark C. Miller, Mon Feb 26 21:37:13 PST 2007
//    Made it deal with older SAMI files where mesh_data did not include
//    the 'ndims' member (entry [7]) and was assumed 3.
//
//    Mark C. Miller, Mon Nov 28 16:49:15 PST 2011
//    Moved bulk of code having to do with opening file and reading metadata
//    to populate database metadata. Added use of ExceptionGenerator.
// ****************************************************************************

avtSAMIFileFormat::avtSAMIFileFormat(const char *filename)
    : avtSTSDFileFormat(filename)
{
    xVals = 0;
    yVals = 0;
    zVals = 0;

    dbFile = 0;

    nzones = 0; 
    nshells = 0;
    nbeams = 0;
    nnodes = 0; 
    nmats  = 0;
    nslides = 0;
    iorigin = 0;
    ndims  = 0;

    //
    // Global Silo Library Calls. Note we don't DBForceSingle(1) here. This
    // plugin is designed to read native data and let VisIt convert as needed
    //
    DBShowErrors(DB_ALL, ExceptionGenerator);
#ifdef E_CHECKSUM
    DBSetEnableChecksums(1);
#endif

}

// ****************************************************************************
//  Method: InitFile
//
//  Purpose: Ensure file is opened and essential metadata queried out of it.
//
//  Programmer: Mark C. Miller, Tue Nov 29 11:38:01 PST 2011
// ****************************************************************************

void
avtSAMIFileFormat::InitFile()
{
    if (dbFile) return;

    if ((dbFile = DBOpen((char *)filename, DB_UNKNOWN, DB_READ)) == 0)
    {
        EXCEPTION1(InvalidFilesException, filename);
    }

    int *meshData = (int *) DBGetVar(dbFile, "mesh_data");
    int meshDataLen = DBGetVarLength(dbFile, "mesh_data");
    if (meshData == 0)
    {
        char tmpMsg[512];
        snprintf(tmpMsg, sizeof(tmpMsg), "Unable to read \"mesh_data\" from %s", filename);
        EXCEPTION1(InvalidFilesException, tmpMsg);
    }

    nzones = meshData[0];
    nshells = meshData[1];
    nbeams = meshData[2];
    nnodes = meshData[3];
    nmats  = meshData[4];
    nslides = meshData[5];
    iorigin = meshData[6];
    if (meshDataLen < 8)
        ndims = 3;
    else
        ndims  = meshData[7];
    free(meshData);

    //
    // To properly create avtMaterialMetaData, we need to know all materials
    //
    int *globalMeshData = (int *) DBGetVar(dbFile, "global_mesh_data");
    if (globalMeshData)
    {
        nmats  = globalMeshData[4];
        free(globalMeshData);
    }
}


// ****************************************************************************
//  Method: avtSAMIFileFormat::FreeUpResources
//
//  Purpose:
//      When VisIt is done focusing on a particular timestep, it asks that
//      timestep to free up any resources (memory, file descriptors) that
//      it has associated with it.  This method is the mechanism for doing
//      that.
//
//  Programmer: miller -- generated by xml2avt
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
// ****************************************************************************

void
avtSAMIFileFormat::FreeUpResources(void)
{
    if (dbFile)
        DBClose(dbFile);
    dbFile = 0;

    nzones = 0; 
    nshells = 0;
    nbeams = 0;
    nnodes = 0; 
    nmats  = 0;
    nslides = 0;
    iorigin = 0;
    ndims  = 0;

    if (xVals) free(xVals);
    if (yVals) free(yVals);
    if (zVals) free(zVals);

    xVals = 0;
    yVals = 0;
    zVals = 0;
}


// ****************************************************************************
//  Method: avtSAMIFileFormat::PopulateDatabaseMetaData
//
//  Purpose:
//      This database meta-data object is like a table of contents for the
//      file.  By populating it, you are telling the rest of VisIt what
//      information it can request from you.
//
//  Programmer: miller -- generated by xml2avt
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
//  Modifications:
//
//    Mark C. Miller, Mon Nov 28 16:49:48 PST 2011
//    Moved bulk of code to open a database and examine metadata from
//    constructor to here.
//
//    Mark C. Miller, Tue Nov 29 11:39:02 PST 2011
//    Moved code to open file and examine metadata to InitFile
// ****************************************************************************

void
avtSAMIFileFormat::PopulateDatabaseMetaData(avtDatabaseMetaData *md)
{
    int i;

    InitFile();

    //
    // Hard code the main mesh object into the metadata
    //
    avtMeshMetaData *mmd = new avtMeshMetaData("mesh", 1, 0, 0,
                                   0, ndims, ndims, AVT_UNSTRUCTURED_MESH);
    md->Add(mmd);

    //
    // Add nodesets as point meshes if we have 'em
    //
    DBtoc *toc;
    if ((DBSetDir(dbFile, "nodesets") == 0) && ((toc = DBGetToc(dbFile)) != 0))
    {
        for (i = 0; i < toc->nvar; i++)
        {
            char tmpName[256];
            snprintf(tmpName, sizeof(tmpName), "nodesets/%s", toc->var_names[i]);
            avtMeshMetaData *nsmmd = new avtMeshMetaData(tmpName, 1, 0, 0,
                                           0, ndims, ndims, AVT_POINT_MESH);
            md->Add(nsmmd);
        }
        DBSetDir(dbFile, "..");
    }

    //
    // Add Slide meshes if we have 'em
    //
    toc = DBGetToc(dbFile);

    // copy just the dir entries from the TOC
    int toc_copy_ndir = toc->ndir;
    char **toc_copy_dir_names = (char **) malloc(toc->ndir * sizeof(char*));
    for (i = 0; i < toc->ndir; i++)
        toc_copy_dir_names[i] = strdup(toc->dir_names[i]);

    if (toc != 0)
    {
        for (i = 0; i < toc_copy_ndir; i++)
        {
            if (strncmp(toc_copy_dir_names[i], "slide_", 6) == 0)
            {
                char tmpName[256];

                // old way
                // Unforunately, I don't think we can ever remove this if we intend to support
                // older SAMI files. Those files will likely not ever be updated and we loose
                // ability to plot them if we remove this block.
                snprintf(tmpName, sizeof(tmpName), "/%s/master/face_node0", toc_copy_dir_names[i]);
                if (DBInqVarExists(dbFile, tmpName))
                {
                    snprintf(tmpName, sizeof(tmpName), "%s/master", toc_copy_dir_names[i]);
                    avtMeshMetaData *smmmd = new avtMeshMetaData(tmpName, 1, 0, 0,
                                               0, ndims, ndims-1, AVT_UNSTRUCTURED_MESH);
                    md->Add(smmmd);
                    snprintf(tmpName, sizeof(tmpName), "%s/slave", toc_copy_dir_names[i]);
                    avtMeshMetaData *ssmmd = new avtMeshMetaData(tmpName, 1, 0, 0,
                                                   0, ndims, ndims-1, AVT_UNSTRUCTURED_MESH);
                    md->Add(ssmmd);
                    continue;
                }

                // new way
                snprintf(tmpName, sizeof(tmpName), "/%s/sideA/face_node0", toc_copy_dir_names[i]);
                if (DBInqVarExists(dbFile, tmpName))
                {
                    snprintf(tmpName, sizeof(tmpName), "%s/sideA", toc_copy_dir_names[i]);
                    avtMeshMetaData *smmmd = new avtMeshMetaData(tmpName, 1, 0, 0,
                                               0, ndims, ndims-1, AVT_UNSTRUCTURED_MESH);
                    md->Add(smmmd);
                    snprintf(tmpName, sizeof(tmpName), "%s/sideB", toc_copy_dir_names[i]);
                    avtMeshMetaData *ssmmd = new avtMeshMetaData(tmpName, 1, 0, 0,
                                                   0, ndims, ndims-1, AVT_UNSTRUCTURED_MESH);
                    md->Add(ssmmd);
                }
            }
        }
    }

    for (i = 0; i < toc_copy_ndir; i++)
        free(toc_copy_dir_names[i]);

    //
    // Add a material object, if present
    //
    if (nmats && DBInqVarExists(dbFile, "brick_material"))
    {
        vector<string> matnames;
        for (int i = 0; i < nmats; i++)
        {
            char tmp[16];
            snprintf(tmp, sizeof(tmp), "%03d", i+1);
            matnames.push_back(tmp);
        }
        avtMaterialMetaData *matmd = new avtMaterialMetaData("mat", "mesh",
                                             nmats, matnames);
        md->Add(matmd);
    }

    if (DBInqVarExists(dbFile, "global_node_numbers"))
    {
        avtScalarMetaData *smd = new avtScalarMetaData("global_node_numbers",
            "mesh", AVT_NODECENT);
        md->Add(smd);
    }
}


// ****************************************************************************
//  Method: avtSAMIFileFormat::GetMesh
//
//  Purpose:
//      Gets the mesh associated with this file.  The mesh is returned as a
//      derived type of vtkDataSet (ie vtkRectilinearGrid, vtkStructuredGrid,
//      vtkUnstructuredGrid, etc).
//
//  Arguments:
//      meshname    The name of the mesh of interest.  This can be ignored if
//                  there is only one mesh.
//
//  Programmer: miller -- generated by xml2avt
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
//  Modifications:
//    Mark C. Miller, Mon Oct 16 13:20:06 PDT 2006
//    Made it more tolerant of Silo read errors and more descriptive error
//    messages.
//
//    Hank Childs, Thu Sep  6 13:23:20 PDT 2007
//    Fix indexing problem.
//
//    Mark C. Miller, Tue Nov 29 11:39:21 PST 2011
//    Added call to InitFile
// ****************************************************************************

vtkDataSet *
avtSAMIFileFormat::GetMesh(const char *meshname)
{
    InitFile();

    int i,j;
    int siloType = DBGetVarType(dbFile, "x");
    int vtkType = SiloTypeToVTKType(siloType);
    int typeSize = SizeOfType(vtkType);

    if (xVals == 0)
    {
        xVals = (char*) malloc(nnodes * typeSize);
        if (DBReadVar(dbFile, "x", xVals) != 0)
        {
            char tmpMsg[512];
            snprintf(tmpMsg, sizeof(tmpMsg), "Unable to read \"x\" from %s", filename);
            EXCEPTION1(InvalidFilesException, tmpMsg);
        }
    }
    if (yVals == 0)
    {
        yVals = (char*) malloc(nnodes * typeSize);
        if (DBReadVar(dbFile, "y", yVals) != 0)
        {
            char tmpMsg[512];
            snprintf(tmpMsg, sizeof(tmpMsg), "Unable to read \"y\" from %s", filename);
            EXCEPTION1(InvalidFilesException, tmpMsg);
        }
    }
    if (ndims == 3 && zVals == 0)
    {
        zVals = (char*) malloc(nnodes * typeSize);
        if (DBReadVar(dbFile, "z", zVals) != 0)
        {
            char tmpMsg[512];
            snprintf(tmpMsg, sizeof(tmpMsg), "Unable to read \"z\" from %s", filename);
            EXCEPTION1(InvalidFilesException, tmpMsg);
        }
    }

    //
    // Populate the points structure with xyz triples
    //
    vtkPoints *points  = vtkPoints::New();
    points->SetDataType(vtkType);
    points->SetNumberOfPoints(nnodes);
    char *pts = (char *) points->GetVoidPointer(0);
    for (i = 0; i < nnodes; i++)
    {
        char *tmp = pts + typeSize * 3 * i;
        memcpy(tmp, xVals + typeSize * i, typeSize);
        tmp += typeSize;
        memcpy(tmp, yVals + typeSize * i, typeSize);
        tmp += typeSize;
        if (ndims == 3)
            memcpy(tmp, zVals + typeSize * i, typeSize);
        else
            memset(tmp, 0, typeSize); // all 0's is valid zero for any type
        tmp += typeSize;
    }

    vtkUnstructuredGrid *ugrid = vtkUnstructuredGrid::New();
    ugrid->SetPoints(points);
    points->Delete();

    if (strncmp(meshname, "nodesets/", 9) == 0)
    {
        int nssize = DBGetVarLength(dbFile, (char *)meshname);
        if (nssize == 0)
        {
            char tmpMsg[512];
            snprintf(tmpMsg, sizeof(tmpMsg), "nodeset \"%s\" is size zero", meshname);
            EXCEPTION1(InvalidFilesException, tmpMsg);
        }

        int *nodelist = (int *) DBGetVar(dbFile, (char *)meshname);
        if (nodelist == 0)
        {
            char tmpMsg[512];
            snprintf(tmpMsg, sizeof(tmpMsg), "unable to read \"%s\"", meshname);
            EXCEPTION1(InvalidFilesException, tmpMsg);
        }
        
        for (i = 0; i < nssize; i++)
        {
            vtkIdType id = (vtkIdType) nodelist[i];
            ugrid->InsertNextCell(VTK_VERTEX, 1, &id);
        }

        free(nodelist);

        return ugrid;
    }
    else if (strncmp(meshname, "slide_", 6) == 0)
    {
        int nlsize = 0, *nd[4] = {0,0,0,0}; (void) nlsize; //TODO: check fix for uninitialized value
        for (j = 0; j < (1<<(ndims-1)); j++)
        {
            char tmpName[256];
            snprintf(tmpName, sizeof(tmpName), "%s/face_node%d", meshname, j);
            nlsize = DBGetVarLength(dbFile, tmpName);
            if (nlsize == 0)
            {
                char tmpMsg[512];
                snprintf(tmpMsg, sizeof(tmpMsg), "slide \"%s\" is size zero", meshname);
                EXCEPTION1(InvalidFilesException, tmpMsg);
            }
            nd[j] = (int *) DBGetVar(dbFile, tmpName);
            if (nd[j] == 0)
            {
                char tmpMsg[512];
                snprintf(tmpMsg, sizeof(tmpMsg), "unable to read \"%s\"", meshname);
                EXCEPTION1(InvalidFilesException, tmpMsg);
            }
        }

        for (i = 0; i < nlsize; i++)
        {
            vtkIdType verts[4];
            for (j = 0; j < (1<<(ndims-1)); j++)
                verts[j] = (vtkIdType) nd[j][i] - iorigin;
            if (ndims-1 == 2)
                ugrid->InsertNextCell(VTK_QUAD, 4, verts);
            else
                ugrid->InsertNextCell(VTK_LINE, 2, verts);
        }

        for (j = 0; j < (1<<(ndims-1)); j++)
            if (nd[j]) free(nd[j]);

        return ugrid;
    }

    //
    // Assume its the main mesh
    //
    int *nd[8] = {0,0,0,0,0,0,0,0};
    for (j = 0; j < (1<<ndims); j++)
    {
        char tmpName[32];
        snprintf(tmpName, sizeof(tmpName), "brick_nd%d", j);
        nd[j] = (int *) DBGetVar(dbFile, tmpName);
        if (nd[j] == 0)
        {
            char tmpMsg[512];
            snprintf(tmpMsg, sizeof(tmpMsg), "unable to read \"%s\"", tmpName);
            EXCEPTION1(InvalidFilesException, tmpMsg);
        }
    }

    //
    // Populate the ugrid structure with either quads or hexs
    //
    for (i = 0; i < nzones; i++)
    {
        vtkIdType verts[8];
        for (j = 0; j < (1<<ndims); j++)
            verts[j] = (vtkIdType) nd[j][i] - iorigin;

        if (ndims == 3)
            ugrid->InsertNextCell(VTK_HEXAHEDRON, 8, verts);
        else
            ugrid->InsertNextCell(VTK_QUAD, 4, verts);
    }

    for (j = 0; j < (1<<ndims); j++)
        free(nd[j]);

    return ugrid;
}

// ****************************************************************************
//  Method: avtSAMIFileFormat::GetAuxiliaryData
//
//  Arguments:
//      varname    The name of the variable requested.
//
//  Programmer: Mark C. Miller 
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
//  Modifications:
//
//    Mark C. Miller, Tue Nov 29 11:39:21 PST 2011
//    Added call to InitFile
// ****************************************************************************

void *
avtSAMIFileFormat::GetAuxiliaryData(const char *var,
    const char *type, void *, DestructorFunction &df)
{
    InitFile();

    void *rv = NULL;
    if (strcmp(type, AUXILIARY_DATA_MATERIAL) == 0)
    {
        rv = (void *) GetMaterial(var);
        df = avtMaterial::Destruct;
    }
    return rv;
}

// ****************************************************************************
//  Method: avtSAMIFileFormat::GetMaterial
//
//  Arguments:
//      varname    The name of the variable requested.
//
//  Programmer: Mark C. Miller 
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
//  Modifications:
//    Mark C. Miller, Mon Oct 16 13:20:06 PDT 2006
//    Made it more tolerant of Silo read error.
//
//    Mark C. Miller, Tue Nov 29 11:39:21 PST 2011
//    Added call to InitFile
// ****************************************************************************

avtMaterial *
avtSAMIFileFormat::GetMaterial(const char *varname)
{
    InitFile();

    int i;
    int *matList = (int *) DBGetVar(dbFile, "brick_material");

    if (matList == 0)
        return 0;

    int *matnos = new int[nmats];
    char **matnames = new char*[nmats];
    for (i = 0; i < nmats; i++)
    {
        char tmp[16];
        matnos[i] = i+1;
        snprintf(tmp, sizeof(tmp), "%03d", i+1);
        matnames[i] = strdup(tmp);
    }

    avtMaterial *mat = new avtMaterial(nmats, matnos, matnames,
                                       1, &nzones, 0, matList,
                                       0, 0, 0, 0, 0, "Domain 0");

    for (i = 0; i < nmats; i++)
        free(matnames[i]);
    delete [] matnames;
    free(matList);
    delete [] matnos;

    return mat;
}

// ****************************************************************************
//  Method: avtSAMIFileFormat::GetVar
//
//  Purpose:
//      Gets a scalar variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      varname    The name of the variable requested.
//
//  Programmer: miller -- generated by xml2avt
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
//  Modifications:
//
//    Mark C. Miller, Tue Nov 29 11:39:21 PST 2011
//    Added call to InitFile
// ****************************************************************************

vtkDataArray *
avtSAMIFileFormat::GetVar(const char *varname)
{
    InitFile();

    if (strcmp(varname, "global_node_numbers") == 0)
    {
        vtkIntArray *gn = vtkIntArray::New();
        gn->SetNumberOfTuples(nnodes);
        if (DBReadVar(dbFile, "global_node_numbers", gn->GetVoidPointer(0)) != 0)
        {
            gn->Delete();
            return 0;
        }
        return gn;
    }
    return 0;
}


// ****************************************************************************
//  Method: avtSAMIFileFormat::GetVectorVar
//
//  Purpose:
//      Gets a vector variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      varname    The name of the variable requested.
//
//  Programmer: miller -- generated by xml2avt
//  Creation:   Wed Oct 11 13:40:57 PST 2006
//
// ****************************************************************************

vtkDataArray *
avtSAMIFileFormat::GetVectorVar(const char *varname)
{
    return 0;
}
